package com.zhiqian.community.config;

import com.zhiqian.community.entity.User;
import com.zhiqian.community.service.UserService;
import com.zhiqian.community.util.CommunityConstant;
import com.zhiqian.community.util.CommunityUtil;
import com.zhiqian.community.util.CookieUtil;
import com.zhiqian.community.util.RedisKeyUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter implements CommunityConstant {
    @Autowired
    private UserService userService;
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 配置认证规则
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new AuthenticationProvider() {
            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                String username = authentication.getName();
                String password = (String) authentication.getCredentials();
                User user = userService.getUserByName(username);
                if (user == null) {
                    throw new UsernameNotFoundException("用户名或密码不正确");
                }
                password = CommunityUtil.MD5(password + user.getSalt());
                if (!user.getPassword().equals(password)) {
                    throw new BadCredentialsException("用户名或密码不正确");
                }
                if (user.getStatus() == 0){
                    throw new UsernameNotFoundException("该账号未激活");
                }
                return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
            }

            @Override
            public boolean supports(Class<?> aClass) {
                return UsernamePasswordAuthenticationToken.class.equals(aClass);
            }
        });
    }

    /**
     * 忽略静态资源的访问
     *
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/**");
    }

    /**
     * 配置授权规则
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //登陆配置
        http.formLogin().loginPage("/loginPage").loginProcessingUrl("/login")
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
                        SavedRequest savedRequest = requestCache.getRequest(request, response);
                        if (savedRequest!=null) {
                            String url = savedRequest.getRedirectUrl();
                            response.sendRedirect(url);
                        }else {
                            response.sendRedirect(request.getContextPath()+"/index");
                        }
                    }
                })
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
                        request.setAttribute("errMsg", e.getMessage());
                        request.getRequestDispatcher("/loginPage").forward(request, response);
                    }
                });
        //退出登陆配置
        http.logout().logoutUrl("/logout").logoutSuccessHandler(new LogoutSuccessHandler() {
            @Override
            public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                response.sendRedirect(request.getContextPath() + "/index");
            }
        });
        //记住我
        http.rememberMe().tokenRepository(new InMemoryTokenRepositoryImpl())
                .tokenValiditySeconds(3600 * 24 * 7)
                .userDetailsService(userService);

        //请求授权
        http.authorizeRequests()
                .antMatchers(
                        "/comment/addComment/**",
                        "/doLike",
                        "/message/**",
                        "/discuss/addOrUpdate",
                        "/discuss/delete/**",
                        "/discuss/toAdd",
                        "/discuss/draft",
                        "/discuss/toUpdate/**",
                        "/follow",
                        "/followerDetail/**",
                        "/followeeDetail/**",
                        "/user/setting",
                        "/user/upload",
                        "/user/updatePwd",
                        "/user/profile/**",
                        "/file/**",
                        "/share/**"
                )
                .hasAnyAuthority(AUTHORITY_USER, AUTHORITY_ADMIN, AUTHORITY_MODERATOR)
                .antMatchers("/discuss/stick/**","/discuss/quintessence/**")
                .hasAnyAuthority(AUTHORITY_ADMIN,AUTHORITY_MODERATOR)
                .antMatchers("/admin/**","/actuator/**")
                .hasAnyAuthority(AUTHORITY_MODERATOR)
                .anyRequest().permitAll()
                .and().csrf().disable();//关闭csrf检查

        //权限不够时的处理
        http.exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPoint() {
            //没有登陆
            @Override
            public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
                String xRequestedWith = request.getHeader("x-requested-with");
                if ("XMLHttpRequest".equals(xRequestedWith)){
                    response.setContentType("application/plain;charset=utf-8");
                    PrintWriter writer = response.getWriter();
                    writer.write(CommunityUtil.getJSONString(403,"你还没有登陆哦，请登陆后再试吧~"));
                }else {
                    response.sendRedirect(request.getContextPath() + "/loginPage");
                }
            }
        }).accessDeniedHandler(new AccessDeniedHandler() {
            //权限不足
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
                String xRequestedWith = request.getHeader("x-requested-with");
                if ("XMLHttpRequest".equals(xRequestedWith)){
                    response.setContentType("application/plain;charset=utf-8");
                    PrintWriter writer = response.getWriter();
                    writer.write(CommunityUtil.getJSONString(403,"权限不足"));
                }else {
                    response.sendRedirect(request.getContextPath() + "/denied");
                }
            }
        });

        //登陆验证码校验过滤器
        http.addFilterBefore(new Filter() {
            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                HttpServletRequest request = (HttpServletRequest) servletRequest;
                HttpServletResponse response = (HttpServletResponse) servletResponse;
                if (request.getServletPath().equals("/login")) {
                    String code = request.getParameter("code");
                    String owner = CookieUtil.getCookieValue(request, "owner");
                    String kaptchaKey = RedisKeyUtil.getKaptchaKey(owner);
                    String kaptcha = (String) redisTemplate.opsForValue().get(kaptchaKey);
                    if (StringUtils.isBlank(kaptcha) || !kaptcha.equalsIgnoreCase(code)) {
                        request.setAttribute("codeMsg", "验证码错误");
                        request.getRequestDispatcher("/loginPage").forward(request, response);
                        return;
                    }
                }
                filterChain.doFilter(request, response);
            }
        }, UsernamePasswordAuthenticationFilter.class);

    }
}
